import { Component } from 'react'
import { View, Text, Input, Image, Canvas } from '@tarojs/components'
import Taro from '@tarojs/taro'
import './index.less'
import ElemList from './components/ElemList'
import Toolbar from './components/Toolbar'
import IconPicker from './components/IconPicker'
import TextEditorModal from './components/TextEditorModal'
import CropOverlay from './components/CropOverlay'

// 创建页面：电子手账编辑器（微信小程序/H5）
// 主要功能：
// - 上传图片（使用小程序 API）
// - 选择内置图标并添加到画布
// - 添加文本（弹窗富文本输入）
// - 触摸拖拽、双指缩放与旋转（移动端手势）
// - 元素列表（选中、删除、上移/下移、置顶/置底、复制、锁定/隐藏）
// 注意：导出到图片时需要处理远程图片下载（此处为原型示例）
export default class CreatPage extends Component {
  // 元素数据结构：{ id, type, src?, text?, x, y, scale, rotate, width, height, locked, hidden }
  state: any = {
    elements: [] as any[],
    selectedId: null as string | null,
    // 画布尺寸
    canvasW: 320,
    canvasH: 480,
    // 画布可视视口高度（父级容器高度，不随加/减纸变化）
    canvasViewportH: 480,
    // 画布滚动偏移（用于在父视口内滚动大画布）
    canvasScrollY: 0,
    // 分页线位置数组（相对于画布顶部的 y 坐标）
    pageDividers: [] as number[],
    // 临时拖拽状态
    dragging: false,
    dragOffset: { x: 0, y: 0 },
    // 手势状态（双指缩放/旋转）
    gesture: null as null | {
      id: string
      startDist: number
      startAngle: number
      startScale: number
      startRotate: number
    },
    // 文本内联编辑
    editingId: null as string | null,
    editingValue: '',
    // 是否显示图标选择弹窗
    showIconPicker: false,
    // 文本编辑弹窗控制与输入内容
    showTextEditor: false,
    textEditorValue: '',
    // 文本渲染临时画布尺寸（保留用于其它逻辑）
    textCanvasW: 10,
    textCanvasH: 10,
    // 内联裁剪控件已移除（使用画布内裁剪）
    // 画布内裁剪状态
    inCanvasCropping: false,
    inCanvasCropTargetId: null as string | null,
    inCanvasCropBox: { x: 0, y: 0, w: 0, h: 0 },
    inCanvasCropMode: '' as '' | 'move' | 'resize' | 'resize-handle' | 'rotate',
    inCanvasCropPointerId: null as number | null,
    cropCanvasW: 0,
    cropCanvasH: 0,
    inCanvasCropHandle: null as string | null,
    inCanvasCropAngle: 0,
  }

  // 处理角点按下，开始缩放交互
  onCropHandleDown = (handle: string, ev: any) => {
    ev.stopPropagation && ev.stopPropagation()
    const touch = ev.touches?.[0]
    this.setState({ inCanvasCropMode: handle === 'rot' ? 'rotate' : 'resize-handle', inCanvasCropPointerId: touch?.identifier ?? null, inCanvasCropHandle: handle })
  }

  // 旋转手柄按下
  onCropRotateHandleDown = (ev: any) => {
    ev.stopPropagation && ev.stopPropagation()
    const touch = ev.touches?.[0]
    // 记录起始角度
    const rect = (this.state as any).canvasRect || { left: 0, top: 0 }
    const cx = (this.state as any).inCanvasCropBox.x + (this.state as any).inCanvasCropBox.w / 2
    const cy = (this.state as any).inCanvasCropBox.y + (this.state as any).inCanvasCropBox.h / 2
    const px = (touch?.clientX ?? touch?.pageX ?? 0) - rect.left
    const py = (touch?.clientY ?? touch?.pageY ?? 0) - rect.top
    const ang = Math.atan2(py - cy, px - cx) * 180 / Math.PI
    this.setState({
      inCanvasCropMode: 'rotate-handle',
      inCanvasCropPointerId: touch?.identifier ?? null,
      inCanvasCropRotateStartAngle: (this.state as any).inCanvasCropAngle || 0,
      inCanvasCropPointerStartAngle: ang,
    })
  }

  // 处理裁剪框按下，开始移动交互
  onCropBoxDown = (ev: any) => {
    ev.stopPropagation && ev.stopPropagation()
    const touch = ev.touches?.[0]
    this.setState({ inCanvasCropMode: 'move', inCanvasCropPointerId: touch?.identifier ?? null })
  }

  // 内置图标分组（示例），用于图标弹窗选择（按选项卡展示）
  iconCategories: Record<string, string[]> = {
    常用: [
      'https://img.icons8.com/emoji/96/000000/sun-emoji.png',
      'https://img.icons8.com/emoji/96/000000/red-heart.png',
      'https://img.icons8.com/emoji/96/000000/star-emoji.png',
    ],
    自然: [
      'https://img.icons8.com/emoji/96/000000/seedling-emoji.png',
      'https://img.icons8.com/emoji/96/000000/moon-emoji.png',
      'https://img.icons8.com/emoji/96/000000/fire-emoji.png',
    ],
    表情: [
      'https://img.icons8.com/emoji/96/000000/grinning-face.png',
      'https://img.icons8.com/emoji/96/000000/smiling-face-with-sunglasses.png',
    ]
  }

  // 当前图标弹窗选中的选项卡
  iconActiveTab = Object.keys(this.iconCategories)[0]

  // 打开/关闭图标选择弹窗
  toggleIconPicker = (show?: boolean) => {
    this.setState(({ showIconPicker }: any) => ({ showIconPicker: typeof show === 'boolean' ? show : !showIconPicker }))
  }

  // 从弹窗选择图标并添加到画布
  onSelectIcon = (url: string) => {
    this.addIcon(url)
    this.toggleIconPicker(false)
  }

  // 文本编辑弹窗开关
  toggleTextEditor = (show?: boolean) => {
    this.setState(({ showTextEditor }: any) => ({ showTextEditor: typeof show === 'boolean' ? show : !showTextEditor }))
  }

  // 确认文本输入并添加到画布
  confirmTextEditor = (style?: any) => {
    const { textEditorValue } = this.state as any
    if (textEditorValue && textEditorValue.trim()) {
      // 新增文本支持样式（由 TextEditorModal 返回）
      this.addText(textEditorValue.trim(), style)
    }
    this.setState({ textEditorValue: '', showTextEditor: false })
  }

  // componentDidMount：初始化画布尺寸并获取画布在屏幕上的位置信息，便于触摸坐标计算
  componentDidMount() {
    // 小程序/移动端适配：使用 Taro 获取屏幕宽度并计算画布尺寸
    try {
      const sys = Taro.getSystemInfoSync()
      const w = Math.min(390, sys.windowWidth - 24)
      const h = Math.round(w * 1.4)
      this.setState({ canvasW: w, canvasH: h, canvasViewportH: h }, () => {
        // 获取画布父级视口在屏幕上的位置，后续用于触摸坐标转换
        const query = Taro.createSelectorQuery()
        query.select('.creat-page__canvas-viewport').boundingClientRect((rect: any) => {
          this.setState({ canvasRect: rect || { left: 0, top: 0 } })
        }).exec()
      })
    } catch (e) {
      // 兜底
      this.setState({ canvasW: 320, canvasH: 480, canvasViewportH: 480, canvasScrollY: 0, canvasRect: { left: 0, top: 0 } })
    }
  }

  // 加纸：每次调用将画布高度增加 200px
  addPaper = () => {
    this.setState((prev: any) => {
      const prevH = prev.canvasH || 0
      const nextDividers = (prev.pageDividers || []).concat([prevH])
      return { canvasH: prevH + 200, pageDividers: nextDividers }
    })
  }

  // 减纸：每次调用将画布高度减少 200px，最小高度为 200px
  removePaper = () => {
    this.setState((prev: any) => {
      const prevH = prev.canvasH || 0
      const prevDividers = (prev.pageDividers || []).slice()
      if (prevDividers.length > 0) {
        prevDividers.pop()
        return { canvasH: Math.max(200, prevH - 200), pageDividers: prevDividers }
      }
      return { canvasH: Math.max(200, prevH - 200) }
    })
  }

  // 生成唯一 id
  uid() {
    return Date.now().toString(36) + Math.random().toString(36).slice(2, 8)
  }

  // 选择图片（优先使用小程序 API），添加到画布
  // 保持图片原始纵横比：先获取图片 natural size，再按画布限制等比缩放后添加
  chooseImage = async () => {
    try {
      const res = await Taro.chooseImage({ count: 1, sourceType: ['album', 'camera'] })
      const path = res.tempFilePaths?.[0]
      if (!path) return

      try {
        const info: any = await Taro.getImageInfo({ src: path })
        const imgW = info.width || 0
        const imgH = info.height || 0
        // canvasW/canvasH 在本函数后续未直接读取，但保留以便未来使用
        const { canvasW, canvasH } = this.state as any

        // 计算最大展示尺寸（沿用原先默认比例，但保持不变形）
        const maxW = Math.floor(canvasW * 0.6)
        const maxH = Math.floor(canvasH * 0.6)

        let width = imgW
        let height = imgH
        if (imgW > 0 && imgH > 0) {
          const ratio = imgW / imgH
          // 先限制最大宽度
          if (width > maxW) {
            width = maxW
            height = Math.round(maxW / ratio)
          }
          // 再限制最大高度
          if (height > maxH) {
            height = maxH
            width = Math.round(maxH * ratio)
          }
        } else {
          // 若无法获取尺寸则使用默认基于画布的尺寸
          width = Math.floor(canvasW * 0.6)
          height = Math.floor(canvasW * 0.4)
        }

        // 保证不超过画布边界
        width = Math.min(width, canvasW)
        height = Math.min(height, canvasH)

        this.addElement({ type: 'image', src: path, width, height, naturalWidth: imgW, naturalHeight: imgH })
      } catch (e) {
        // 获取图片信息失败，退回到原始行为
        this.addElement({ type: 'image', src: path })
      }
    } catch (err) {
      // ignore
    }
  }

  // 添加图标（使用内置图标 URL 或 emoji）
  addIcon = (iconSrc: string) => {
    this.addElement({ type: 'icon', src: iconSrc })
  }

  // 添加文本元素到画布
  addText = (text: string, style?: any) => {
    if (!text) return
    const textStyle = Object.assign({ backgroundColor: '#ffffff', borderType: 'solid', borderColor: '#000000', borders: { top: false, left: false, right: false, bottom: false } }, style || {})
    this.addElement({ type: 'text', text, textStyle })
  }

  // 通用添加元素入口，设置默认位置与变换
  // payload 可包含 type/src/text/width/height 等字段
  addElement = (payload: any) => {
    const id = this.uid()
    const el = Object.assign({
      id,
      x: 12,
      y: 12,
      scale: 1,
      rotate: 0,
      width: Math.floor(this.state.canvasW * 0.6),
      height: Math.floor(this.state.canvasW * 0.4),
      locked: false,
      hidden: false,
    }, payload)
    // 新元素预置在列表最前面，方便立即可见和编辑
    this.setState(({ elements }: any) => ({ elements: [el, ...elements], selectedId: id }))
  }

  // 选中元素
  selectElement = (id: string) => {
    this.setState({ selectedId: id })
  }

  // 开始在画布内裁剪指定元素：显示裁剪框并进入裁剪模式
  startInCanvasCrop = (id: string) => {
    const el = (this.state as any).elements.find((x: any) => x.id === id)
    if (!el || el.type !== 'image') return
    // 基于视觉尺寸（考虑 scale，不考虑旋转）默认中心 60% 区域
    const s = el.scale || 1
    const visW = (el.width || 100) * s
    const visH = (el.height || 80) * s
    const visLeft = (el.x || 0) + Math.round(((el.width || 0) - (el.width || 0) * s) / 2)
    const visTop = (el.y || 0) + Math.round(((el.height || 0) - (el.height || 0) * s) / 2)
    const w = Math.max(80, Math.round(visW * 0.6))
    const h = Math.max(60, Math.round(visH * 0.6))
    const x = visLeft + Math.round((visW - w) / 2)
    const y = visTop + Math.round((visH - h) / 2)
    this.setState({ inCanvasCropping: true, inCanvasCropTargetId: id, inCanvasCropBox: { x, y, w, h }, inCanvasCropAngle: 0 })
  }

  // 取消画布内裁剪
  cancelInCanvasCrop = () => {
    this.setState({ inCanvasCropping: false, inCanvasCropTargetId: null, inCanvasCropBox: { x: 0, y: 0, w: 0, h: 0 }, inCanvasCropMode: '', inCanvasCropAngle: 0 })
  }

  // 确认画布内裁剪：使用隐藏的 cropCanvas 导出裁剪后的图片并替换元素 src
  confirmInCanvasCrop = async () => {
    const { inCanvasCropTargetId, inCanvasCropBox, inCanvasCropAngle } = this.state as any
    const el = (this.state as any).elements.find((x: any) => x.id === inCanvasCropTargetId)
    if (!el) return
    try {
      Taro.showLoading({ title: '剪裁中…', mask: true })
      // device pixel ratio
      const sys = Taro.getSystemInfoSync()
      const dpr = sys.pixelRatio || 1

      // 原始像素优先，fallback 到元素宽高
      const naturalW = el.naturalWidth && el.naturalWidth > 0 ? el.naturalWidth : (el.width && el.width > 0 ? el.width : 1)
      const naturalH = el.naturalHeight && el.naturalHeight > 0 ? el.naturalHeight : (el.height && el.height > 0 ? el.height : 1)

      // 如果是远程 URL，先下载到本地（小程序需要本地路径以确保 drawImage 正常工作）
      let localSrc = el.src || ''
      if (/^https?:\/\//.test(localSrc)) {
        try {
          const d: any = await Taro.downloadFile({ url: localSrc })
          if (d && d.tempFilePath) localSrc = d.tempFilePath
        } catch (err) {
          console.warn('downloadFile failed, try drawing remote src directly', err)
        }
      }

      // 视觉 -> 自然像素换算（考虑元素缩放与旋转）
      const scale = el.scale || 1
      const fx = (el.width || 1) > 0 ? (naturalW / (el.width || 1)) : 1
      const fy = (el.height || 1) > 0 ? (naturalH / (el.height || 1)) : 1
      const Cx = inCanvasCropBox.x + inCanvasCropBox.w / 2
      const Cy = inCanvasCropBox.y + inCanvasCropBox.h / 2
      const angleRad = (inCanvasCropAngle || 0) * Math.PI / 180

      // 目标图尺寸（自然像素）
      const destW = Math.max(1, Math.round((inCanvasCropBox.w / Math.max(scale, 0.0001)) * fx))
      const destH = Math.max(1, Math.round((inCanvasCropBox.h / Math.max(scale, 0.0001)) * fy))
      const canvasPixelW = Math.max(1, Math.round(destW * dpr))
      const canvasPixelH = Math.max(1, Math.round(destH * dpr))

      this.setState({ cropCanvasW: canvasPixelW, cropCanvasH: canvasPixelH }, () => {
        Taro.nextTick(async () => {
          try {
            const ctx: any = Taro.createCanvasContext('cropCanvas', this)
            try { ctx.scale(dpr, dpr) } catch (_) {}

            // 将输出坐标原点置于中心，并设置裁剪区域为输出矩形
            ctx.translate(destW / 2, destH / 2)
            ctx.beginPath()
            ctx.rect(-destW / 2, -destH / 2, destW, destH)
            try { ctx.clip && ctx.clip() } catch (_) {}

            // 从自然像素 -> 输出坐标的复合变换
            // 1) 输出缩放：overlay 尺寸映射到输出尺寸
            ctx.scale(destW / inCanvasCropBox.w, destH / inCanvasCropBox.h)
            // 2) 逆向旋转到裁剪框本地坐标
            ctx.rotate(-angleRad)
            // 3) 平移到裁剪框中心
            ctx.translate(el.x - Cx, el.y - Cy)
            // 4) 元素自身：围绕中心旋转+缩放
            ctx.translate((el.width || 0) / 2, (el.height || 0) / 2)
            ctx.rotate((el.rotate || 0) * Math.PI / 180)
            ctx.scale(scale, scale)
            ctx.translate(-(el.width || 0) / 2, -(el.height || 0) / 2)
            // 5) 自然像素 -> 元素本地（未缩放尺寸）
            ctx.scale(1 / Math.max(fx, 0.0001), 1 / Math.max(fy, 0.0001))

            // 绘制源图（自然像素）
            ctx.drawImage(localSrc, 0, 0, naturalW, naturalH)

            ctx.draw(false, async () => {
              try {
                const r: any = await Taro.canvasToTempFilePath({
                  canvasId: 'cropCanvas',
                  x: 0,
                  y: 0,
                  width: canvasPixelW,
                  height: canvasPixelH,
                  destWidth: canvasPixelW,
                  destHeight: canvasPixelH,
                  fileType: 'png',
                  quality: 1
                }, this)
                const newSrc = r.tempFilePath
                const newNaturalW = destW
                const newNaturalH = destH
                const cb = inCanvasCropBox
                this.setState(({ elements }: any) => ({
                  elements: elements.map((e: any) => e.id === inCanvasCropTargetId ? {
                    ...e,
                    src: newSrc,
                    x: cb.x,
                    y: cb.y,
                    width: cb.w,
                    height: cb.h,
                    naturalWidth: newNaturalW,
                    naturalHeight: newNaturalH,
                    scale: e.scale,
                    rotate: e.rotate,
                  } : e),
                  inCanvasCropping: false,
                  inCanvasCropTargetId: null,
                  inCanvasCropBox: { x: 0, y: 0, w: 0, h: 0 },
                  cropCanvasW: 0,
                  cropCanvasH: 0,
                  inCanvasCropAngle: 0,
                }))
              } catch (err) {
                console.error('canvasToTempFilePath error', err)
                Taro.showToast({ title: '裁剪导出失败', icon: 'none' })
              } finally {
                Taro.hideLoading()
              }
            })
          } catch (err) {
            console.error('drawImage error', err)
            Taro.showToast({ title: '裁剪失败', icon: 'none' })
            Taro.hideLoading()
          }
        })
      })
    } catch (err) {
      console.error('confirmInCanvasCrop unexpected error', err)
      Taro.showToast({ title: '裁剪失败', icon: 'none' })
      Taro.hideLoading()
    }
  }

  // 进入/退出剪裁模式（图片/文本）
  // 画布内裁剪入口
  openCropperForElement = (id: string) => {
    // 触发画布内裁剪，不再打开覆盖层或跳转到单独页面
    this.startInCanvasCrop(id)
  }

  initInlineCropper() {
    // 已移除 WeCropper 相关逻辑
  }

  handleConfirmCrop = () => {
    // 已移除 WeCropper 相关逻辑
  }

  handleCancelCrop = () => {
    // 已移除 WeCropper 相关逻辑
  }

  // 自定义裁剪逻辑已移除（使用第三方裁剪页）

  // 删除元素
  deleteElement = (id: string) => {
    this.setState(({ elements, selectedId }: any) => ({
      elements: elements.filter((e: any) => e.id !== id),
      selectedId: selectedId === id ? null : selectedId,
    }))
  }

  // 层级上移/下移（通过数组顺序决定渲染层级）
  moveElement = (id: string, dir: 'up' | 'down') => {
    this.setState(({ elements }: any) => {
      const idx = elements.findIndex((e: any) => e.id === id)
      if (idx === -1) return { elements }
      const newEls = elements.slice()
      // 按列表视觉顺序：上移 = 向上（索引 - 1），下移 = 向下（索引 + 1）
      if (dir === 'up' && idx > 0) {
        const t = newEls[idx - 1]; newEls[idx - 1] = newEls[idx]; newEls[idx] = t
      }
      if (dir === 'down' && idx < newEls.length - 1) {
        const t = newEls[idx + 1]; newEls[idx + 1] = newEls[idx]; newEls[idx] = t
      }
      return { elements: newEls }
    })
  }

  // 置顶 / 置底
  toTop = (id: string) => {
    this.setState(({ elements }: any) => {
      const idx = elements.findIndex((e: any) => e.id === id)
      if (idx === -1) return { elements }
      const el = elements[idx]
      const rest = elements.filter((_: any, i: number) => i !== idx)
      // 将元素移动到数组开头（视觉上为“置顶”）
      return { elements: [el, ...rest] }
    })
  }

  toBottom = (id: string) => {
    this.setState(({ elements }: any) => {
      const idx = elements.findIndex((e: any) => e.id === id)
      if (idx === -1) return { elements }
      const el = elements[idx]
      const rest = elements.filter((_: any, i: number) => i !== idx)
      // 将元素移动到数组末尾（视觉上为“置底”）
      return { elements: [...rest, el] }
    })
  }

  // 锁定/隐藏/复制
  toggleLock = (id: string) => {
    this.setState(({ elements }: any) => ({ elements: elements.map((el: any) => el.id === id ? { ...el, locked: !el.locked } : el) }))
  }
  toggleHide = (id: string) => {
    this.setState(({ elements }: any) => ({ elements: elements.map((el: any) => el.id === id ? { ...el, hidden: !el.hidden } : el) }))
  }
  duplicate = (id: string) => {
    this.setState(({ elements }: any) => {
      const base = elements.find((e: any) => e.id === id)
      if (!base) return { elements }
      const newEl = { ...base, id: this.uid(), x: base.x + 16, y: base.y + 16 }
      // 将复制的元素也放到列表最前面
      return { elements: [newEl, ...elements], selectedId: newEl.id }
    })
  }

  // 拖拽开始
  onPointerDown = (ev: any, el: any) => {
    ev.stopPropagation()
    if (el.locked) return
    // 如果正在进行画布内裁剪且点击的是目标元素，则进入裁剪操作（移动/调整裁剪框）
    const { inCanvasCropping, inCanvasCropTargetId } = this.state as any
    if (inCanvasCropping && inCanvasCropTargetId === el.id) {
      const touch = ev.touches?.[0]
      const clientX = touch?.clientX ?? touch?.pageX ?? ev.clientX ?? ev.pageX
      const clientY = touch?.clientY ?? touch?.pageY ?? ev.clientY ?? ev.pageY
      const rect = (this.state as any).canvasRect || { left: 0, top: 0 }
      const localX = clientX - rect.left
      const localY = clientY - rect.top
      const cb = (this.state as any).inCanvasCropBox
      const handleSize = 16
      // 检测是否点击在四个角点的区域，优先判定为缩放
      const corners = [
        { x: cb.x, y: cb.y, name: 'nw' },
        { x: cb.x + cb.w, y: cb.y, name: 'ne' },
        { x: cb.x, y: cb.y + cb.h, name: 'sw' },
        { x: cb.x + cb.w, y: cb.y + cb.h, name: 'se' },
      ]
      for (const c of corners) {
        if (Math.abs(localX - c.x) <= handleSize && Math.abs(localY - c.y) <= handleSize) {
          this.setState({ inCanvasCropMode: 'resize-handle', inCanvasCropPointerId: touch?.identifier ?? null, inCanvasCropHandle: c.name })
          ev.preventDefault?.()
          return
        }
      }
      // 否则进入移动模式
      this.setState({ inCanvasCropMode: 'move', inCanvasCropPointerId: touch?.identifier ?? null })
      ev.preventDefault?.()
      return
    }
    const touches = ev.touches
    if (touches && touches.length === 2) {
      const p1 = touches[0]; const p2 = touches[1]
      const x1 = p1.clientX ?? p1.pageX
      const y1 = p1.clientY ?? p1.pageY
      const x2 = p2.clientX ?? p2.pageX
      const y2 = p2.clientY ?? p2.pageY
      const dx = x2 - x1
      const dy = y2 - y1
      const dist = Math.sqrt(dx * dx + dy * dy)
      const angle = Math.atan2(dy, dx) * 180 / Math.PI
      this.setState({
        gesture: {
          id: el.id,
          startDist: dist,
          startAngle: angle,
          startScale: el.scale,
          startRotate: el.rotate,
        },
        selectedId: el.id,
      })
      return
    }
    ev.preventDefault?.()
    const { canvasRect } = this.state as any
    const rect = canvasRect || { left: 0, top: 0 }
    const startX = ev.touches?.[0]?.clientX ?? ev.touches?.[0]?.pageX ?? ev.clientX ?? ev.pageX
    const startY = ev.touches?.[0]?.clientY ?? ev.touches?.[0]?.pageY ?? ev.clientY ?? ev.pageY
    // 使用元素当前状态计算偏移，避免依赖 getBoundingClientRect（小程序端不可用）
    const offsetX = startX - rect.left - el.x
    const offsetY = startY - rect.top - el.y
    this.setState({ dragging: el.id, dragOffset: { x: offsetX, y: offsetY }, selectedId: el.id })
  }

  // 小程序画布 touchMove 捕获，防止页面滚动
  handleCanvasTouchMove = (e: any) => {
    // 不再阻止冒泡/默认，这样父视口可以接收 touchMove 用于滚动画布
    // 仍然保留元素拖拽处理
    this.onPointerMove(e)
  }

  // 画布 touchStart: 不处理裁剪相关逻辑（已迁移到第三方裁剪页）
  handleCanvasTouchStart = () => {
    // 如果当前有选中元素，点击空白处应开始拖动该元素
    // 记录触摸起始 Y 用于父级视口滚动（若未开始拖拽元素）
    this._viewportTouchStartY = null
  }

  // 父视口触摸滚动状态（私有）
  _viewportTouchStartY: number | null = null

  handleViewportTouchStart = (e: any) => {
    const touches = e.touches
    if (!touches || touches.length !== 1) return
    const t = touches[0]
    // Only prepare for viewport scroll when no element is selected
    const { selectedId } = this.state as any
    if (!selectedId) this._viewportTouchStartY = t.clientY ?? t.pageY
  }

  handleViewportTouchMove = (e: any) => {
    if (this._viewportTouchStartY == null) return
    const touches = e.touches
    if (!touches || touches.length !== 1) return
    const t = touches[0]
    const y = t.clientY ?? t.pageY
    const dy = this._viewportTouchStartY - y
    if (Math.abs(dy) < 2) return
    e.preventDefault?.()
    // 更新滚动偏移并限制在 [0, canvasH - viewportH]
    this.setState(({ canvasScrollY, canvasH, canvasViewportH }: any) => {
      const maxScroll = Math.max(0, (canvasH || 0) - (canvasViewportH || 0))
      // If an element is selected, do not scroll viewport here
      const { selectedId } = this.state as any
      if (selectedId) return null
      let next = (canvasScrollY || 0) + dy
      next = Math.max(0, Math.min(maxScroll, next))
      this._viewportTouchStartY = y
      return { canvasScrollY: next }
    })
  }

  handleViewportTouchEnd = () => {
    this._viewportTouchStartY = null
  }

  handleCanvasTouchEnd = (e: any) => {
    e.stopPropagation && e.stopPropagation()
    this.onPointerUp()
  }

  // 拖拽移动
  onPointerMove = (ev: any) => {
    const { dragging, dragOffset, canvasW, canvasH, gesture, selectedId, elements, canvasRect, inCanvasCropping, inCanvasCropTargetId, inCanvasCropBox, inCanvasCropMode, inCanvasCropHandle } = this.state as any
    const touches = ev.touches
    // 画布内裁剪：处理裁剪框移动
    if (inCanvasCropping && inCanvasCropTargetId && inCanvasCropMode) {
      ev.preventDefault?.()
      const clientX = ev.touches?.[0]?.clientX ?? ev.clientX ?? ev.pageX
      const clientY = ev.touches?.[0]?.clientY ?? ev.clientY ?? ev.pageY
      const rect = canvasRect || { left: 0, top: 0 }
      const x = clientX - rect.left
      const y = clientY - rect.top
      const targetEl = elements.find((e: any) => e.id === inCanvasCropTargetId)
      const scale = targetEl ? (targetEl.scale || 1) : 1
      // Visual bounds considering scale (rotation is ignored for clamp simplicity)
      const visW = targetEl ? (targetEl.width || 0) * scale : 0
      const visH = targetEl ? (targetEl.height || 0) * scale : 0
      const imgLeft = targetEl ? (targetEl.x || 0) + Math.round(((targetEl.width || 0) - visW) / 2) : 0
      const imgTop = targetEl ? (targetEl.y || 0) + Math.round(((targetEl.height || 0) - visH) / 2) : 0
      const imgRight = imgLeft + visW
      const imgBottom = imgTop + visH
      const minBoxSize = 20
      if (inCanvasCropMode === 'move') {
        // 将裁剪框移动
        const w = Math.max(minBoxSize, inCanvasCropBox.w || 100)
        const h = Math.max(minBoxSize, inCanvasCropBox.h || 100)
        const nx = Math.max(imgLeft, Math.min(imgRight - w, x - w / 2))
        const ny = Math.max(imgTop, Math.min(imgBottom - h, y - h / 2))
        this.setState({ inCanvasCropBox: { ...inCanvasCropBox, x: nx, y: ny } })
      } else if (inCanvasCropMode === 'rotate') {
        // 绕裁剪框中心旋转
        const cx = inCanvasCropBox.x + inCanvasCropBox.w / 2
        const cy = inCanvasCropBox.y + inCanvasCropBox.h / 2
        const ang = Math.atan2(y - cy, x - cx) * 180 / Math.PI
        this.setState({ inCanvasCropAngle: ang })
      } else if (inCanvasCropMode === 'resize-handle') {
        // 基于 handle 名称调整对应角的坐标与宽高（不考虑旋转，仅对包围盒生效）
        const handle = inCanvasCropHandle
        let { x: bx, y: by, w: bw, h: bh } = inCanvasCropBox
        if (handle === 'nw') {
          const nx = Math.max(imgLeft, Math.min(bx + bw - minBoxSize, x))
          const ny = Math.max(imgTop, Math.min(by + bh - minBoxSize, y))
          const nw = Math.max(minBoxSize, Math.min(imgRight - bx, bw + (bx - nx)))
          const nh = Math.max(minBoxSize, Math.min(imgBottom - by, bh + (by - ny)))
          this.setState({ inCanvasCropBox: { x: nx, y: ny, w: nw, h: nh } })
        }
        if (handle === 'ne') {
          const nx2 = Math.max(bx + minBoxSize, Math.min(x, imgRight))
          const ny2 = Math.max(imgTop, Math.min(by + bh - minBoxSize, y))
          const nw2 = Math.max(minBoxSize, Math.min(imgRight - bx, nx2 - bx))
          const nh2 = Math.max(minBoxSize, Math.min(imgBottom - ny2, bh + (by - ny2)))
          this.setState({ inCanvasCropBox: { x: bx, y: ny2, w: nw2, h: nh2 } })
        }
        if (handle === 'sw') {
          const nx3 = Math.max(imgLeft, Math.min(bx + bw - minBoxSize, x))
          const ny3 = Math.max(by + minBoxSize, Math.min(y, imgBottom))
          const nw3 = Math.max(minBoxSize, Math.min(imgRight - nx3, bw + (bx - nx3)))
          const nh3 = Math.max(minBoxSize, Math.min(imgBottom - by, ny3 - by))
          this.setState({ inCanvasCropBox: { x: nx3, y: by, w: nw3, h: nh3 } })
        }
        if (handle === 'se') {
          const nx4 = Math.max(bx + minBoxSize, Math.min(x, imgRight))
          const ny4 = Math.max(by + minBoxSize, Math.min(y, imgBottom))
          const nw4 = Math.max(minBoxSize, Math.min(imgRight - bx, nx4 - bx))
          const nh4 = Math.max(minBoxSize, Math.min(imgBottom - by, ny4 - by))
          this.setState({ inCanvasCropBox: { x: bx, y: by, w: nw4, h: nh4 } })
        }
      }
      return
    }
    // 双指手势：缩放+旋转
    if (gesture && touches && touches.length === 2 && selectedId === gesture.id) {
      const p1 = touches[0]; const p2 = touches[1]
      const x1 = p1.clientX ?? p1.pageX
      const y1 = p1.clientY ?? p1.pageY
      const x2 = p2.clientX ?? p2.pageX
      const y2 = p2.clientY ?? p2.pageY
      const dx = x2 - x1
      const dy = y2 - y1
      const dist = Math.sqrt(dx * dx + dy * dy)
      const angle = Math.atan2(dy, dx) * 180 / Math.PI
      const scale = Math.max(0.2, Math.min(3, gesture.startScale * (dist / gesture.startDist)))
      const rotate = (gesture.startRotate + (angle - gesture.startAngle)) % 360
      this.setState({ elements: elements.map((el: any) => el.id === gesture.id ? { ...el, scale, rotate } : el) })
      return
    }
    // 如果拖拽进行中，且用户在移动过程中加入第二个手指，则切换为手势模式
    if (!gesture && dragging && touches && touches.length === 2) {
      const p1 = touches[0]; const p2 = touches[1]
      const x1 = p1.clientX ?? p1.pageX
      const y1 = p1.clientY ?? p1.pageY
      const x2 = p2.clientX ?? p2.pageX
      const y2 = p2.clientY ?? p2.pageY
      const dx = x2 - x1
      const dy = y2 - y1
      const dist = Math.sqrt(dx * dx + dy * dy)
      const angle = Math.atan2(dy, dx) * 180 / Math.PI
      const current = elements.find((el: any) => el.id === dragging)
      if (current) {
        this.setState({
          gesture: {
            id: current.id,
            startDist: dist,
            startAngle: angle,
            startScale: current.scale,
            startRotate: current.rotate,
          },
          dragging: false,
          selectedId: current.id,
        })
        return
      }
    }
    if (!dragging) return
    ev.preventDefault?.()
    const clientX = ev.touches?.[0]?.clientX ?? ev.touches?.[0]?.pageX ?? ev.clientX ?? ev.pageX
    const clientY = ev.touches?.[0]?.clientY ?? ev.touches?.[0]?.pageY ?? ev.clientY ?? ev.pageY
    const rect = canvasRect || { left: 0, top: 0 }
    let x = clientX - rect.left - dragOffset.x
    let y = clientY - rect.top - dragOffset.y
    // 边界约束（考虑缩放后尺寸）
    const current = elements.find((el: any) => el.id === dragging)
    if (current) {
      const w = current.width * current.scale
      const h = current.height * current.scale
      x = Math.max(0, Math.min(x, canvasW - w))
      y = Math.max(0, Math.min(y, canvasH - h))
    }
    this.setState({ elements: elements.map((el: any) => el.id === dragging ? { ...el, x, y } : el) })
  }

  // 裁剪交互逻辑已移除；使用独立裁剪页面处理裁剪交互

  // 拖拽结束
  onPointerUp = () => {
    this.setState({ dragging: false, gesture: null, inCanvasCropMode: '' })
    // 如果裁剪操作存在并为 draw/resize/move，结束该操作但保留裁剪框
    if ((this.state as any).croppingOp) {
      this.setState({ croppingOp: null })
    }
  }

  // NOTE: 手动缩放/旋转控件已从工具栏移除，操作通过手势完成，此函数保留以备扩展

  // 用于获取 canvas DOM
  canvasRef: any = null
  // 内联裁剪器已移除

  // 将当前编辑内容绘制到 canvas 并导出图片（小程序）
  exportToImage = async () => {
    const { elements, canvasW, canvasH } = this.state as any
    try {
      const ctx: any = Taro.createCanvasContext('exportCanvas', this)
      // 背景白底
      ctx.setFillStyle('#ffffff')
      ctx.fillRect(0, 0, canvasW, canvasH)

      // 逐层绘制元素（按照数组顺序）
      for (const el of elements) {
        if (el.hidden) continue
        if (el.type === 'image' || el.type === 'icon') {
          // 小程序中 drawImage 需要本地路径或已经在小程序允许的域名下
          // 对于远程 URL，生产环境应先下载到本地再绘制。
          try {
            ctx.save()
            ctx.translate(el.x + el.width / 2, el.y + el.height / 2)
            ctx.rotate((el.rotate || 0) * Math.PI / 180)
            ctx.scale(el.scale || 1, el.scale || 1)
            ctx.drawImage(el.src, -el.width / 2, -el.height / 2, el.width, el.height)
            ctx.restore()
          } catch (e) {
            // 绘制单张失败时忽略
          }
        }
        if (el.type === 'text') {
          const ts = el.textStyle || {}
          ctx.save()
          ctx.translate(el.x + (el.width || 0) / 2, el.y + (el.height || 0) / 2)
          ctx.rotate((el.rotate || 0) * Math.PI / 180)
          ctx.scale(el.scale || 1, el.scale || 1)
          // draw background
          if (ts.backgroundColor) {
            ctx.setFillStyle(ts.backgroundColor)
            ctx.fillRect(-el.width / 2, -el.height / 2, el.width, el.height)
          }
          // draw borders
          const borderColor = ts.borderColor || '#000'
          ctx.setStrokeStyle(borderColor)
          ctx.setLineWidth(2)
          // canvas on small program may not support dashed/dotted; skip pattern for now
          if (ts.borders) {
            if (ts.borders.top) { ctx.beginPath(); ctx.moveTo(-el.width / 2, -el.height / 2); ctx.lineTo(el.width / 2, -el.height / 2); ctx.stroke() }
            if (ts.borders.left) { ctx.beginPath(); ctx.moveTo(-el.width / 2, -el.height / 2); ctx.lineTo(-el.width / 2, el.height / 2); ctx.stroke() }
            if (ts.borders.right) { ctx.beginPath(); ctx.moveTo(el.width / 2, -el.height / 2); ctx.lineTo(el.width / 2, el.height / 2); ctx.stroke() }
            if (ts.borders.bottom) { ctx.beginPath(); ctx.moveTo(-el.width / 2, el.height / 2); ctx.lineTo(el.width / 2, el.height / 2); ctx.stroke() }
          }
          // draw text
          ctx.setFillStyle('#222')
          ctx.setFontSize(20)
          ctx.fillText(el.text || '', -((el.text || '').length * 10) / 2, 0)
          ctx.restore()
        }
      }

      // draw 后导出临时文件
      ctx.draw(false, async () => {
        try {
          await Taro.canvasToTempFilePath({
            canvasId: 'exportCanvas',
            x: 0,
            y: 0,
            width: canvasW,
            height: canvasH,
            destWidth: canvasW,
            destHeight: canvasH,
            fileType: 'png',
            quality: 1,
            success: (r: any) => {
              // 自动保存到相册（需用户授权）
              Taro.saveImageToPhotosAlbum({ filePath: r.tempFilePath }).then(() => {
                Taro.showToast({ title: '已保存到相册', icon: 'success' })
              }).catch(() => {
                Taro.showModal({ title: '导出成功', content: `图片已生成：${r.tempFilePath}` })
              })
            },
            fail: () => {
              Taro.showToast({ title: '导出失败', icon: 'none' })
            }
          }, this)
        } catch (e) {
          Taro.showToast({ title: '导出异常', icon: 'none' })
        }
      })
    } catch (e) {
      Taro.showToast({ title: '导出出错', icon: 'none' })
    }
  }

  render() {
    const { elements, selectedId, canvasW, canvasH, editingId, editingValue } = this.state as any
    return (
      <View className='creat-page'>
        <View className='creat-page__left'>
          <Toolbar onChooseImage={this.chooseImage} onOpenIconPicker={() => this.toggleIconPicker(true)} onOpenTextEditor={() => this.toggleTextEditor(true)} onAddPaper={this.addPaper} onRemovePaper={this.removePaper} />

          <View className='creat-page__canvas-viewport' style={{ width: canvasW, height: this.state.canvasViewportH, position: 'relative', overflow: 'hidden' }} onTouchStart={this.handleViewportTouchStart} onTouchMove={this.handleViewportTouchMove} onTouchEnd={this.handleViewportTouchEnd}>
            <View className='creat-page__canvas' style={{ width: canvasW, height: canvasH, position: 'relative', transform: `translateY(${-(this.state.canvasScrollY || 0)}px)` }} ref={r => (this.canvasRef = r)} onTouchStart={this.handleCanvasTouchStart} onTouchMove={this.handleCanvasTouchMove} onTouchEnd={this.handleCanvasTouchEnd}>
            {/* 渲染元素：按列表顺序决定 zIndex，列表越靠前的元素 zIndex 越大（更在上层） */}
            {elements.map((el: any, idx: number) => {
              const isActive = el.id === selectedId
              // 列表越靠前（idx 越小），zIndex 值越大；最后一个元素 zIndex 最小（底层）
              const zIndex = Math.max(1, elements.length - idx)
              const style = {
                position: 'absolute' as any,
                left: el.x,
                top: el.y,
                // Always preserve transform to avoid visual change when entering cropping
                transform: `translate(0,0) rotate(${el.rotate}deg) scale(${el.scale})`,
                width: el.width,
                height: el.height,
                cursor: 'move',
                zIndex
              }
              return (
                <View key={el.id} className={`canvas-el ${isActive ? 'is-active' : ''} ${el.type === 'image' ? 'is-image' : ''}`} style={style} onTouchStart={(e: any) => this.onPointerDown(e, el)} onClick={(e: any) => { e.stopPropagation && e.stopPropagation(); this.selectElement(el.id) }}>
                  {!el.hidden && el.type === 'image' && <Image src={el.src} style={{ width: '100%', height: '100%', objectFit: 'fill' }} />}
                  {!el.hidden && el.type === 'icon' && <Image src={el.src} style={{ width: '100%', height: '100%' }} />}
                  {!el.hidden && el.type === 'text' && (() => {
                    const ts = el.textStyle || {}
                    const baseStyle: any = {
                      background: ts.backgroundColor || '#fff',
                      display: 'flex',
                      alignItems: 'center',
                      justifyContent: 'center',
                      width: '100%',
                      height: '100%',
                      boxSizing: 'border-box',
                    }
                    // Build inline border style string
                    const borderColor = ts.borderColor || '#000'
                    const borderType = ts.borderType || 'solid'
                    const borders = ts.borders || {}
                    const borderStyles: any = {}
                    if (borders.top) borderStyles.borderTop = `2px ${borderType} ${borderColor}`
                    if (borders.left) borderStyles.borderLeft = `2px ${borderType} ${borderColor}`
                    if (borders.right) borderStyles.borderRight = `2px ${borderType} ${borderColor}`
                    if (borders.bottom) borderStyles.borderBottom = `2px ${borderType} ${borderColor}`
                    const combinedStyle = Object.assign({}, baseStyle, borderStyles)
                    return (
                      editingId === el.id ? (
                        <Input
                          value={editingValue}
                          onInput={(e: any) => this.setState({ editingValue: e.detail.value })}
                          onBlur={() => {
                            const val = editingValue
                            this.setState(({ elements }: any) => ({
                              elements: elements.map((x: any) => x.id === el.id ? { ...x, text: val } : x),
                              editingId: null,
                              editingValue: ''
                            }))
                          }}
                        />
                      ) : (
                        <View style={combinedStyle} onLongPress={() => this.setState({ editingId: el.id, editingValue: el.text || '' })}>
                          <Text style={{ fontSize: 20 * el.scale }}>{el.text}</Text>
                        </View>
                      )
                    )
                  })()}
                </View>
              )
            })}
            {/* 渲染分页线（在新增区域的上边界处绘制） */}
            {(this.state.pageDividers || []).map((y: number, i: number) => {
              const top = y
              return (
                <View key={`divider-${i}`} style={{ position: 'absolute', left: 0, right: 0, top, height: 0, pointerEvents: 'none' }}>
                  <View style={{ borderTop: '1px dashed rgba(0,0,0,0.4)', margin: '0 12px' }} />
                  <View style={{ position: 'absolute', left: '50%', transform: 'translateX(-50%)', top: -10, background: 'rgba(255,255,255,0.9)', padding: '0 6px', fontSize: 12, color: '#666' }}>分页线</View>
                </View>
              )
            })}
            {/* 点击空白区域取消选中 */}
            <View onClick={() => this.setState({ selectedId: null })} style={{ position: 'absolute', left: 0, top: 0, right: 0, bottom: 0 }} />
            {/* 裁剪交互封装为组件，渲染在画布之上 */}
            {this.state.inCanvasCropping && this.state.inCanvasCropTargetId && (
              <CropOverlay
                box={this.state.inCanvasCropBox}
                angle={this.state.inCanvasCropAngle}
                onBoxDown={this.onCropBoxDown}
                onHandleDown={this.onCropHandleDown}
                onCancel={this.cancelInCanvasCrop}
                onConfirm={this.confirmInCanvasCrop}
              />
            )}
            </View>
          </View>
          {/* 隐藏的 canvas，用于导出图片（显式设置绘图缓冲区尺寸，避免坐标失真） */}
          <Canvas
            canvasId='exportCanvas'
            width={canvasW}
            height={canvasH}
            style={{ width: canvasW, height: canvasH, position: 'absolute', left: -9999, top: -9999 }}
          />
          {/* 隐藏的裁剪 canvas（根据需要动态设置大小，避免拉伸失真） */}
          <Canvas
            canvasId='cropCanvas'
            width={this.state.cropCanvasW}
            height={this.state.cropCanvasH}
            style={{ width: this.state.cropCanvasW, height: this.state.cropCanvasH, position: 'absolute', left: -9999, top: -9999 }}
          />
          {/* 文本渲染临时画布 */}
          <Canvas
            canvasId='textRenderCanvas'
            width={this.state.textCanvasW}
            height={this.state.textCanvasH}
            style={{ width: this.state.textCanvasW, height: this.state.textCanvasH, position: 'absolute', left: -9999, top: -9999 }}
          />
          {/* 内联裁剪器覆盖层（在当前页面显示） */}
          {/* 内联裁剪器覆盖层（在当前页面显示） -- 渲染移至页面底部，确保覆盖所有元素 */}
        </View>

        <View className='creat-page__right'>
          <Text className='panel-title'>元素列表</Text>
          <ElemList
            elements={elements}
            selectedId={selectedId}
            onSelect={id => this.selectElement(id)}
            toTop={id => this.toTop(id)}
            toBottom={id => this.toBottom(id)}
            moveElement={(id, dir) => this.moveElement(id, dir)}
            duplicate={id => this.duplicate(id)}
            toggleLock={id => this.toggleLock(id)}
            toggleHide={id => this.toggleHide(id)}
            deleteElement={id => this.deleteElement(id)}
            onCrop={(id: string) => this.openCropperForElement(id)}
            onCutout={(id: string) => {
              // 图片专用抠图：这里只是占位触发，实际抠图逻辑可在这里实现（例如调用第三方抠图 API）
              const el = (this.state as any).elements.find((x: any) => x.id === id)
              if (!el || el.type !== 'image') {
                Taro.showToast({ title: '请选择图片元素', icon: 'none' })
                return
              }
              // 简单提示：抠图功能尚未实现，展示提示
              Taro.showToast({ title: '抠图功能暂未实现（可接入第三方服务）', icon: 'none' })
            }}
          />
        </View>

        {/* 图标选择弹窗（底部弹出） */}
        <IconPicker visible={this.state.showIconPicker} categories={this.iconCategories} activeTab={this.iconActiveTab} onChangeTab={(tab) => { this.iconActiveTab = tab; this.forceUpdate() }} onSelectIcon={this.onSelectIcon} onClose={() => this.toggleIconPicker(false)} onCropAction={() => {
          // 当 IconPicker 的“抠图”被点击：如果当前选中的是图片，则进入裁剪页
          const { selectedId, elements } = this.state as any
          const sel = elements.find((e: any) => e.id === selectedId)
          if (sel && sel.type === 'image') {
            this.openCropperForElement(sel.id)
          } else {
            Taro.showToast({ title: '请先选择一张图片', icon: 'none' })
          }
        }} />
        {/* 裁剪使用画布上的拖拽交互（无弹窗） */}
        {/* 文本编辑弹窗 */}
        <TextEditorModal visible={this.state.showTextEditor} value={this.state.textEditorValue} onChange={(v) => this.setState({ textEditorValue: v })} onCancel={() => this.toggleTextEditor(false)} onConfirm={(style: any) => this.confirmTextEditor(style)} />
        {/* 内联裁剪器已移除：裁剪将在画布内完成 */}
      </View>
    )
  }
}


